 /*
 ------------------------------------------------------------------------------
	Winmad - a test program for using mad library on Windows.

	Copyright (C) 2011 Pierre Veber
	
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software Foundation,
	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
------------------------------------------------------------------------------
*/

#include "stdafx.h"

typedef struct {
	FILE* fp;
	TCHAR* szFilePath;
	struct mad_decoder decoder;
	audio_data_t audio;
	HANDLE hStop;
} decode_thread_data_t;

#define READ_BUF_SIZE	8192
#define CMD_QUIT		0x71

static unsigned char _read_buf[READ_BUF_SIZE];

/*
 * This is the input callback. The purpose of this callback is to (re)fill
 * the stream buffer which is to be decoded. In this example, an entire file
 * has been mapped into memory, so we just call mad_stream_buffer() with the
 * address and length of the mapping. When this callback is called a second
 * time, we are finished decoding.
 */

enum mad_flow fMadInputCallback(void *pData, struct mad_stream *pStream)
{
	size_t read, to_read = READ_BUF_SIZE;
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;
	
	// test exit signal
	if ( WaitForSingleObject(pDecodeThreadData->hStop , 0) == WAIT_OBJECT_0 )
	{
		TRACE("fMadInputCallback - stop signalled, exit playback.\n");
		return MAD_FLOW_STOP;
	}

	// move remaining frames to beginning of buffer
	if (pStream->next_frame != NULL)
	{
		to_read = pStream->next_frame - _read_buf;
		memmove(_read_buf, pStream->next_frame, READ_BUF_SIZE - to_read);
	}
	read = fread(_read_buf + (READ_BUF_SIZE - to_read), 1, to_read, pDecodeThreadData->fp);
	if (read + READ_BUF_SIZE - to_read > 0)
	{
		mad_stream_buffer(
						pStream,
						(const unsigned char*)_read_buf,
						read + READ_BUF_SIZE - to_read
						);
		if (read != 0)
		{
			return MAD_FLOW_CONTINUE;
		}
	}
	return MAD_FLOW_STOP;
}

/*
 * This is the output callback function. It is called after each frame of
 * MPEG audio data has been completely decoded. The purpose of this callback
 * is to output (or play) the decoded PCM audio.
 */

static enum mad_flow fMadOutputCallback(void* pData, struct mad_header const *pHeader, struct mad_pcm *pPcm)
{
	char _buf[4];
	unsigned int nchannels, nsamples;
	mad_fixed_t const *left_ch, *right_ch;
	signed int sample;
	int rc;

	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;
  
	// test exit signal
	if ( WaitForSingleObject(pDecodeThreadData->hStop , 0) == WAIT_OBJECT_0 )
	{
		TRACE("fMadInputCallback - stop signalled, exit playback.\n");
		return MAD_FLOW_STOP;
	}

	nchannels = pPcm->channels;
	nsamples  = pPcm->length;

	left_ch   = pPcm->samples[0];
	right_ch  = pPcm->samples[1];
  
	while (nsamples--)
	{
		/* output sample(s) in 16-bit signed little-endian PCM */
		sample = scale(*left_ch++);
		_buf[0] = (sample >> 0) & 0xff;
		_buf[1] = (sample >> 8) & 0xff;
		sample = scale(*right_ch++);
		_buf[2] = (sample >> 0) & 0xff;
		_buf[3] = (sample >> 8) & 0xff;

		rc = waveWriteIn(&(pDecodeThreadData->audio), _buf, 1);
		/*if (rc != 1)
		{
			fprintf(stderr, "short write, write %d frames\n", rc);
		}*/
	}
	
	return MAD_FLOW_CONTINUE;
}

/*
 * This is the error callback function. It is called whenever a decoding
 * error occurs. The error is indicated by stream->error; the list of
 * possible MAD_ERROR_* errors can be found in the mad.h (or
 * libmad/stream.h) header file.
 */

static
enum mad_flow fMadErrorCallback(void* pData, struct mad_stream* pStream, struct mad_frame* pFrame)
{
	fprintf(stderr, "decoding error 0x%04x (%s)\n", pStream->error, mad_stream_errorstr(pStream));

	/* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */

	return MAD_FLOW_CONTINUE;
}

DWORD WINAPI fDecodeThread(LPVOID lpParameter)
{
	int result;
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)lpParameter;

	/* configure input, output, and error functions */
	mad_decoder_init(
		&pDecodeThreadData->decoder,
		pDecodeThreadData,
        fMadInputCallback,
        0 /* header */,
        0 /* filter */,
        fMadOutputCallback,
        fMadErrorCallback,
        0 /* message */
        );

	/* start decoding */
	result = mad_decoder_run(&pDecodeThreadData->decoder, MAD_DECODER_MODE_SYNC);
	
	return (DWORD)result;
}

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hThread;
	DWORD dwExitCode = 0;
	struct id3_file* id3f;
	int ret = 0;
	int fd;
    decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)malloc(sizeof(decode_thread_data_t));
	memset(pDecodeThreadData, 0, sizeof(decode_thread_data_t));
    
	if (argc > 1)
	{
    
		pDecodeThreadData->fp = NULL;
		pDecodeThreadData->szFilePath = _tcsdup(argv[1]);

		printf("========================================================\n");
		printf("This is Willy-player/PROOF/LEVEL2.\n");
		printf("========================================================\n");
		printf("This program is a simple libmad and wmm application \n");
		printf("which plays a mp3 file.\n");
		printf("Enter q to quit at any time.\n");
	  
		/* init of decode thread */
		hThread = CreateThread(NULL, 0, fDecodeThread, pDecodeThreadData, CREATE_SUSPENDED, NULL);
		if (hThread == NULL)
		{
			DWORD dwErr = GetLastError();
			fprintf(stderr, "Error %d (0x%lx) creating decode thread!\n", dwErr, dwErr);
			ret = 1;
			goto cleanup;
		}

		/* stop event */
		pDecodeThreadData->hStop = CreateEvent(NULL, FALSE, FALSE, _T("Decode Stop Event"));
		if (pDecodeThreadData->hStop == NULL)
		{
			DWORD dwErr = GetLastError();
			fprintf(stderr, "Error %d (0x%lx) creating stop event!\n", dwErr, dwErr);
			ret = 2;
			goto cleanup;
		}

		/* open the file */
		fd = _topen(argv[1], _O_BINARY|_O_RDONLY);
		if (fd == -1)
		{
			fprintf(stderr, "Unable to open: %s !\n", argv[1]);
			ret = 3;
			goto cleanup;
		}

		/* get ID3 tags */
		id3f = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY);
		if (id3f == 0)
		{
			/* __TO_DO
				Not an error, mp3 might not have ID3 tag */
			fprintf(stderr, "Unable to open id3: %s !\n", argv[1]);
			ret = 0x13;
			goto cleanup;
		}
		id3_tag* tag = id3_file_tag(id3f);
		printf("id3 tag version: %x\n", id3_tag_version(tag));
		for (unsigned int i=0; i < tag->nframes; i++)
		{
			id3_frame* id3fr = tag->frames[i];
			printf("Frame #%d: \n\tid: %s\n\tdescription: %s\n\tencoded: %d\n\tfields: %d\n", i, id3fr->id, id3fr->description, id3fr->encoded_length, id3fr->nfields);
			for (unsigned int j=0; j < id3fr->nfields; j++)
			{
				id3_field* id3fi = id3_frame_field(id3fr, j);
				printf("\tField #%d: \n", j);
				
				enum id3_field_type type = id3_field_type(id3fi);
				printf("\t\tType: %d\n", type);
				id3_field_textencoding enc;
				unsigned int num;
				id3_length_t len;
				const id3_byte_t* pData;
				switch (type)
				{
					case ID3_FIELD_TYPE_TEXTENCODING:
						enc = id3_field_gettextencoding(id3fi);
						printf("\t\tText encoding: %d\n", enc);
						break;

					case ID3_FIELD_TYPE_STRINGLIST:
						num = id3_field_getnstrings(id3fi);
						printf("\t\tString list (%d):\n", num);
						for (unsigned int k=0; k < num; k++)
						{
							const id3_ucs4_t* pu = id3_field_getstrings(id3fi, k);
							printf("\t\t\t\"%s\"\n", id3_ucs4_latin1duplicate(pu));
						}
						break;

					case ID3_FIELD_TYPE_STRING:
						printf("\t\t\t\"%s\"\n", id3_ucs4_latin1duplicate(id3_field_getstring(id3fi)));
						break;

					case ID3_FIELD_TYPE_LATIN1:
						printf("\t\t\t\"%s\"\n", id3_field_getlatin1(id3fi));
						break;

					case ID3_FIELD_TYPE_INT8:
						printf("\t\t\t\"%x\"\n", id3_field_getint(id3fi));
						break;

					case ID3_FIELD_TYPE_BINARYDATA:
					{
						FILE* fp = _tfopen(_T("data.bin"), _T("wb"));
						pData = id3_field_getbinarydata(id3fi, &len);
						if (fp != NULL)
						{
							fwrite(pData, 1, len, fp);
							fclose(fp);
						}
						break;
					}
				}

				//printf("\tField #%d: \n\t\tType: %x\n", j, id3_field_type(id3fi));
			}
		}

		/* open the file for reading */
		pDecodeThreadData->fp = _tfdopen(fd, _T("rb"));
		if (pDecodeThreadData->fp == NULL)
		{
			fprintf(stderr, "Unable to fd open: %s !\n", argv[1]);
			ret = 3;
			goto cleanup;
		}

		/* initialize audio engine */
		if ( waveInit(&(pDecodeThreadData->audio)) != 0)
		{
			fprintf(stderr, "Unable to initialize audio !\n");
			ret = 4;
			goto cleanup;
		}

		/* start decode thread */
		ResumeThread(hThread);

		/* main loop */
		while (1)
		{
			int res = fgetc(stdin);
			if ((res == EOF) || (res == CMD_QUIT))
			{
				/* stop decode thread */
				SetEvent(pDecodeThreadData->hStop);
				break;
			}
		}
		
		TRACE( "waiting for Decode thread to exit...\n");
		do
		{
			if ( !GetExitCodeThread(hThread, &dwExitCode) )
			{
				fprintf(stderr, "Unable to stop decode thread !\n");
				ret = 4;
				goto cleanup;
			}
			Sleep(100);
		
		} while (STILL_ACTIVE == dwExitCode);

		cleanup:
		waveClose(&(pDecodeThreadData->audio));
		if (pDecodeThreadData->fp != NULL)
		{
			fclose(pDecodeThreadData->fp);
			id3_file_close(id3f);
		}
	}
	else
	{
		printf("========================================================\n");
		printf("This is Willy-player/PROOF/LEVEL2.\n");
		printf("========================================================\n");
		printf("Please specify a path to mp3 file! \n");
	}	
	
	free(pDecodeThreadData);
	printf("========================================================\n");
    return ret;
}

